package com.singfan.common.db.provider;

import org.dom4j.Element;

import com.singfan.common.ServerEnv;
import com.singfan.common.SingFanException;
import com.singfan.common.db.config.DataBaseType;
import com.singfan.common.db.config.Database;
import com.singfan.common.db.config.MonitorLog;
import com.singfan.common.db.config.Table;
import com.singfan.common.util.CheckUtil;
import com.singfan.common.util.XmlUtil;

import java.io.InputStream;
import java.util.*;

/**
 * 基于XML文件的数据库配置信息提供类
 * Author: HanSongHe
 * Date: 15-7-1 上午11:35
 */
public class XmlDbConfigProvider extends DbConfigProvider {
    private static final String DB_CONFIG_FILE_NAME_TEMP = "singfan-db-config%1$s.xml";

    private Set<String> dataBaseTypeSet = new HashSet<>();

    private String configDatabaseId = null;

    private Map<String, Database> databaseMap = new HashMap<>();
    private Map<String, Table> gameTableMap = new HashMap<>();
    private Map<String, String> gameTableIdMap = new HashMap<>();

    private MonitorLog monitorLog = new MonitorLog();

    @Override
    public String getConfigDatabaseId(){
        return configDatabaseId;
    }

    @Override
    public List<Database> getDataBaseList(){
        List<Database> databaseList = new ArrayList<>();
        Collection<Database> databaseCollection = databaseMap.values();

        for(Database database : databaseCollection){
            databaseList.add(database);
        }

        return databaseList;
    }

    @Override
    public Table getTable(String tableName){
        return gameTableMap.get(tableName);
    }

    @Override
    public MonitorLog getMonitorLog(){
        return monitorLog;
    }

    @Override
    public void init(){
        String configFileName;
        if(ServerEnv.isRelease()){
            configFileName = String.format(DB_CONFIG_FILE_NAME_TEMP, "");
        }
        else{
            configFileName = String.format(DB_CONFIG_FILE_NAME_TEMP, "-" + ServerEnv.getEnv());
        }
        try {
            InputStream is=this.getClass().getClassLoader().getResourceAsStream(configFileName);
            Element configElement = XmlUtil.getRoot(is);
            if(configElement != null){
                Element configDatabaseElement = XmlUtil.getElement(configElement, "config_database", false);
                if(configDatabaseElement != null){
                    Database database = buildDatabase(configDatabaseElement);
                    databaseMap.put(database.getId(), database);

                    configDatabaseId = database.getId();
                }

                Element databasesElement = XmlUtil.getElement(configElement, "app_databases", true);
                List<Element> databaseElementList = XmlUtil.getElementList(databasesElement, "database", true);
                for(Element databaseElement : databaseElementList){
                    Database database = buildDatabase(databaseElement);
                    databaseMap.put(database.getId(), database);
                }

                Element gameTableElement = XmlUtil.getElement(configElement, "app_tables", true);
                List<Element> tableElementList = XmlUtil.getElementList(gameTableElement, "table", true);
                for(Element tableElement : tableElementList){
                    String tableName = XmlUtil.getAttributeValue(tableElement, "name", true);
                    if(gameTableMap.containsKey(tableName)){
                        throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) is repeat !", tableName));
                    }

                    String tableIdText = XmlUtil.getAttributeValue(tableElement, "id", true);
                    if (gameTableIdMap.containsKey(tableIdText)){
                        throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) id(%2$s) is repeat !", tableName, tableIdText));
                    }
                    else {
                        gameTableIdMap.put(tableIdText, tableName);
                    }
                    int tableId;
                    if (CheckUtil.isPositiveInteger(tableIdText)){
                        tableId = Integer.parseInt(tableIdText);
                    }
                    else {
                        throw new SingFanException(String.format("XmlDbConfigProvider.init() error: tableId(%1$s) is error value !", tableIdText));
                    }

                    List<Element> versionElementList = XmlUtil.getElementList(tableElement, "version", true);
                    Map<Integer, List<String>> versionDbsMap = new HashMap<>();
                    Map<Integer, Integer> versionCountMap = new HashMap<>();
                    for(Element versionElement : versionElementList){
                        String number = XmlUtil.getAttributeValue(versionElement, "number", true);
                        if(!CheckUtil.isPositiveInteger(number)){
                            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) version number(%2$s) error !", tableName, number));
                        }

                        int numberValue = Integer.valueOf(number);
                        if(versionDbsMap.containsKey(numberValue)){
                            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) version number(%2$s) repeat !", tableName, number));
                        }

                        String countText = XmlUtil.getAttributeValue(versionElement, "tableCount", true);
                        if (!CheckUtil.isPositiveInteger(countText)){
                            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) version(%2$s) tableCount(%3$s) error !", tableName, countText, countText));
                        }
                        int countValue = Integer.valueOf(countText);
                        versionCountMap.put(numberValue, countValue);


                        String dbs = XmlUtil.getAttributeValue(versionElement, "dbs", true);
                        List<String> dbIdList = new ArrayList<>();
                        String[] dbArr = dbs.split(";");
                        if(dbArr != null && dbArr.length > 0){
                            for(String db : dbArr){
                                String dbId = db.trim();
                                if(databaseMap.containsKey(dbId)){
                                    dbIdList.add(dbId);
                                }
                                else{
                                    throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) version number(%2$s)'s db(%3$s) is not exist in databases !", tableName, number, dbId));
                                }
                            }
                        }

                        if(dbIdList.size() > 0){
                            versionDbsMap.put(numberValue, dbIdList);
                        }
                        else{
                            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: table(%1$s) version number(%2$s)'s dbs is empty !", tableName, number));
                        }
                    }

                    Table table = new Table(tableId, tableName, versionDbsMap, versionCountMap);
                    gameTableMap.put(tableName, table);
                }

                Element monitorLogElement = XmlUtil.getElement(configElement, "monitor_log", true);
                monitorLog.setOnlyWarning(Boolean.parseBoolean(XmlUtil.getAttributeValue(monitorLogElement, "onlyWarning", true)));
                monitorLog.setWarningTime(Integer.valueOf(XmlUtil.getAttributeValue(monitorLogElement, "warningTime", true)));
                monitorLog.setTraceFile(XmlUtil.getAttributeValue(monitorLogElement, "traceFile", true));
                monitorLog.setErrorFile(XmlUtil.getAttributeValue(monitorLogElement, "errorFile", true));

                System.out.println("DbConfig init successful !");
            }
            else{
                throw new SingFanException(String.format("XmlDbConfigProvider.init() error: db_config node is not exist in %1$s !", configFileName));
            }
        }
        catch (SingFanException ex){
            throw ex;
        }
        catch (Exception e) {
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: read %1$s failed %2$s", configFileName, e.getMessage()), e);
        }
    }

    private Database buildDatabase(Element databaseElement){
        String id = XmlUtil.getAttributeValue(databaseElement, "id", true);
        if(databaseMap.containsKey(id)){
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database(%1$s) repeat !", id));
        }
        String type = XmlUtil.getAttributeValue(databaseElement, "type", true);
        if(DataBaseType.isDataBaseType(type)){
            dataBaseTypeSet.add(type);
        }
        else{
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database type(%1$s) is unknown type !", type));
        }
        String dbname = XmlUtil.getAttributeValue(databaseElement, "dbname", true);
        String host = XmlUtil.getAttributeValue(databaseElement, "host", true);
        String port = XmlUtil.getAttributeValue(databaseElement, "port", true);
        if(!CheckUtil.isCustomPort(port)){
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database port(%1$s) is error value !", port));
        }
        String user = XmlUtil.getAttributeValue(databaseElement, "user", true);
        String password = XmlUtil.getAttributeValue(databaseElement, "password", true);

        String readText = XmlUtil.getAttributeValue(databaseElement, "read", true);
        boolean read;
        if (CheckUtil.isBoolean(readText)){
            read = Boolean.parseBoolean(readText);
        }
        else {
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database read(%1$s) is error value !", readText));
        }

        String writeText = XmlUtil.getAttributeValue(databaseElement, "write", true);
        boolean write;
        if (CheckUtil.isBoolean(writeText)){
            write = Boolean.parseBoolean(writeText);
        }
        else {
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database write(%1$s) is error value !", writeText));
        }

        String initialPoolSizeText = XmlUtil.getAttributeValue(databaseElement, "initialPoolSize", true);
        int initialPoolSize;
        if(CheckUtil.isPositiveInteger(initialPoolSizeText)){
            initialPoolSize = Integer.parseInt(initialPoolSizeText);
        }
        else{
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database initialPoolSize(%1$s) is error value !", initialPoolSizeText));
        }

        String minPoolSizeText = XmlUtil.getAttributeValue(databaseElement, "minPoolSize", true);
        int minPoolSize;
        if(CheckUtil.isPositiveInteger(minPoolSizeText)){
            minPoolSize = Integer.parseInt(minPoolSizeText);
        }
        else{
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database minPoolSize(%1$s) is error value !", minPoolSizeText));
        }

        String maxPoolSizeText = XmlUtil.getAttributeValue(databaseElement, "maxPoolSize", true);
        int maxPoolSize;
        if(CheckUtil.isPositiveInteger(maxPoolSizeText)){
            maxPoolSize = Integer.parseInt(maxPoolSizeText);
        }
        else{
            throw new SingFanException(String.format("XmlDbConfigProvider.init() error: database maxPoolSize(%1$s) is error value !", maxPoolSizeText));
        }

        return new Database(id, type, dbname, host, port, user, password, read, write, initialPoolSize, minPoolSize, maxPoolSize);
    }
}
